using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace YouVisio.Silverlight.TextDiff
{
    public class DiffPartsList : ICollection<Operation>, IEnumerable<Operation>, ICollection, IEnumerable
    {
        private object _syncRoot;
        internal int _count;
        internal DiffPartsListNode firstNode;
        internal DiffPartsListNode lastNode;

        internal DiffPartsListNode Find(Operation value)
        {
            var current = this.firstNode;
            while (current != null)
            {
                if (current.value == value)
                {
                    return current;
                }
                current = current.next;
            }
            return null;
        }



        public int Count { get { return this._count; } }

        public string ToDescriptiveString()
        {
            return NodesToDescriptiveString(this.firstNode);
        }

        public TAccumulator FoldGroups<TAccumulator>(TAccumulator accumulator, Func<TAccumulator, Operation, int, TAccumulator> func)
        {
            var current = this.firstNode;
            while (current != null)
            {
                accumulator = func(accumulator, current.value, current.num);
                current = current.next;
            }
            return accumulator;
        }

        internal static string NodesToDescriptiveString(DiffPartsListNode node)
        {
            var sb = new StringBuilder();
            var isFirst = true;
            while (node != null)
            {
                if (!isFirst) sb.Append("; ");
                else isFirst = false;
                sb.Append(node.value);
                sb.Append("=");
                sb.Append(node.num);
                node = node.next;
            }
            return sb.ToString();
        }


        public void AddLast(Operation item)
        {
            this.AddLast(item, 1);
        }

        public void AddLast(Operation item, int times)
        {
            if (times <= 0) return;
            this._count++;
            if (this.firstNode == null)
            {
                this.firstNode = this.lastNode = new DiffPartsListNode { value = item, num = times };
            }
            else if (this.lastNode.value == item)
            {
                this.lastNode.num += times;
            }
            else
            {
                var node = new DiffPartsListNode { value = item, num = times };
                this.lastNode.next = node;
                node.prev = this.lastNode;
                this.lastNode = node;
            }
        }

        #region ICollection and ICollection<Operation>

        public void Add(Operation item)
        {
            this.AddLast(item);
        }

        public void Add(Operation item, int times)
        {
            this.AddLast(item, times);
        }

        public void Clear()
        {
            this.firstNode = null;
            this._count = 0;
        }

        public bool Contains(Operation item)
        {
            return (this.Find(item) != null);
        }

        public void CopyTo(Operation[] array, int arrayIndex)
        {
            if (array == null)
            {
                throw new ArgumentNullException("array");
            }
            if ((arrayIndex < 0) || (arrayIndex > array.Length))
            {
                throw new ArgumentOutOfRangeException("arrayIndex", "Supplied index is outside of range");
            }
            if ((array.Length - arrayIndex) < this.Count)
            {
                throw new ArgumentException("The supplied array is not long enough");
            }
            var current = this.firstNode;
            var num = 1;
            if (current != null)
            {
                do
                {
                    array[arrayIndex++] = current.value;
                    if (current.num == num)
                    {
                        current = current.next;
                        if (num > 1) num = 1;
                    }
                    else
                    {
                        num++;
                    }
                }
                while (current != null);
            }
        }

        public bool Remove(Operation item)
        {
            throw new NotImplementedException();
        }

        public void CopyTo(Array array, int index)
        {
            throw new NotImplementedException();
        }


        int ICollection.Count
        {
            get { return this._count; }
        }

        int ICollection<Operation>.Count
        {
            get { return this._count; }
        }

        bool ICollection<Operation>.IsReadOnly { get { return false; } }
        bool ICollection.IsSynchronized { get { return false; } }
        object ICollection.SyncRoot
        {
            get
            {
                if (this._syncRoot == null)
                {
                    Interlocked.CompareExchange<object>(ref this._syncRoot, new object(), null);
                }
                return this._syncRoot;
            }
        }
        public static DiffPartsList Empty { get { return new DiffPartsList(); } }

        #endregion

        #region Implementation of IEnumerable

        public IEnumerator<Operation> GetEnumerator()
        {
            return new Enumerator(this);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region DiffPartsListNode
        internal sealed class DiffPartsListNode
        {
            internal DiffPartsListNode next;
            internal DiffPartsListNode prev;
            internal int num;
            internal Operation value;
        }
        #endregion


        #region Enumerator
        [StructLayout(LayoutKind.Sequential)]
        public struct Enumerator : IEnumerator<Operation>, IDisposable, IEnumerator
        {
            private readonly DiffPartsList _list;
            private DiffPartsListNode _node;
            private Operation _current;
            private int _index;
            private int _num;

            internal Enumerator(DiffPartsList list)
            {
                this._list = list;
                this._node = list.firstNode;
                this._current = Operation.Unknown;
                this._index = 0;
                this._num = 1;
            }

            public Operation Current
            {
                get { return this._current; }
                set { this._current = value; }
            }
            object IEnumerator.Current
            {
                get
                {
                    if ((this._index == 0) || (this._index == (this._list.Count + 1)))
                    {
                        throw new InvalidOperationException("Enumerator is positioned before the first element or after the last element of the collection.");
                    }
                    return this._current;
                }
            }

            public bool MoveNext()
            {
                if (this._node == null)
                {
                    return false;
                }
                this._index++;
                this._current = this._node.value;
                if (this._node.num == this._num)
                {
                    this._node = this._node.next;
                    if (this._num > 1) this._num = 1;
                }
                else
                {
                    this._num++;
                }
                return true;
            }

            void IEnumerator.Reset()
            {
                this._current = Operation.Unknown;
                this._node = this._list.firstNode;
                this._index = 0;
            }

            public void Dispose()
            {
            }

        }


        #endregion

    }
}